﻿'
' Boogaloo® - http://www.mindfly.com
' Copyright (c) 2008
' by Mindfly, Inc.
'
' Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 
' documentation files (the "Software"), to deal in the Software without restriction, including without limitation 
' the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and 
' to permit persons to whom the Software is furnished to do so, subject to the following conditions:
'
' The above copyright notice and this permission notice shall be included in all copies or substantial portions 
' of the Software.
'
' THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
' INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
' DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
' SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
' SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
' WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
' THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Imports System
Imports System.Xml
Imports System.Xml.XPath
Imports System.Web
Imports System.Net
Imports System.IO
Imports System.Text
Imports System.Text.RegularExpressions

''' <summary>
''' Provides methods for encoding and decoding information exposed by syndicated content. This class cannot be inherited.
''' </summary>
''' <remarks>Very simplified code (translated to VB) from Argotic SyndicationEncodingUtility Class
''' http://www.codeplex.com/Argotic
''' (If you have not checked out the Argotic Syndication Framework it is a must see)
''' </remarks>
Public NotInheritable Class SyndicationEncodingUtility

#Region "WebRequest"



    ''' <summary>
    ''' Returns a HttpWebRequest that makes a request for a resource located at the supplied Uri using the specified credentials and proxy
    ''' </summary>
    ''' <remarks>Simplified code extract from Argotic SyndicationEncodingUtility Class
    ''' http://www.codeplex.com/Argotic
    ''' </remarks>
    Public Overloads Shared Function CreateHttpWebRequest(ByVal source As Uri, ByVal credentials As ICredentials, ByVal proxy As IWebProxy, Optional ByVal useragent As String = "") As HttpWebRequest

        Dim request As HttpWebRequest = Nothing

        If source IsNot Nothing Then

            request = HttpWebRequest.Create(source)
            request.AllowAutoRedirect = True
            request.KeepAlive = True
            If Not String.IsNullOrEmpty(useragent) Then request.UserAgent = useragent

            If credentials IsNot Nothing Then
                request.Credentials = credentials
            Else
                request.UseDefaultCredentials = True
            End If

            If proxy IsNot Nothing Then
                request.Proxy = proxy
            End If


        End If

        Return request

    End Function
    ''' <summary>
    ''' Returns a HttpWebRequest that makes a request for a resource located at the supplied Uri using the specified credentials and proxy
    ''' </summary>
    Public Overloads Shared Function CreateHttpWebRequest(ByVal source As Uri, Optional ByVal useragent As String = "") As HttpWebRequest
        Return CreateHttpWebRequest(source, Nothing, Nothing, useragent)
    End Function

#End Region

#Region "WebResponse"

    ''' <summary>
    ''' Returns the HttpWebResponse to a request for a resource located at the supplied Uri using the specified credentials and proxy
    ''' </summary>
    ''' <remarks>Simplified code extract from Argotic SyndicationEncodingUtility Class
    ''' http://www.codeplex.com/Argotic
    ''' </remarks>
    Public Overloads Shared Function CreateHttpWebResponse(ByVal source As Uri, ByVal credentials As ICredentials, ByVal proxy As IWebProxy, Optional ByVal useragent As String = "") As HttpWebResponse

        Dim response As HttpWebResponse = Nothing

        If source IsNot Nothing Then

            Dim request As HttpWebRequest = CreateHttpWebRequest(source, credentials, proxy, useragent)

            If request IsNot Nothing Then response = request.GetResponse

        End If

        Return response
    End Function

    ''' <summary>
    ''' Returns the HttpWebResponse to a request for a resource located at the supplied Uri using the specified credentials and proxy
    ''' </summary>
    Public Overloads Shared Function CreateHttpWebResponse(ByVal source As Uri, Optional ByVal useragent As String = "") As HttpWebResponse
        Return CreateHttpWebResponse(source, Nothing, Nothing, useragent)
    End Function

#End Region


#Region "XPathNavigator"

    ''' <summary>
    ''' 
    ''' </summary>
    ''' <remarks>Simplified code extract from Argotic SyndicationEncodingUtility Class
    ''' http://www.codeplex.com/Argotic
    ''' </remarks>
    Public Overloads Shared Function CreateSafeNavigator(ByVal source As Uri, ByVal credentials As ICredentials, ByVal proxy As IWebProxy, ByVal charEncoding As Encoding)

        Dim navigator As XPathNavigator = Nothing
        Using response As HttpWebResponse = CreateHttpWebResponse(source, credentials, proxy)

            Dim stream As Stream = Nothing

            If response IsNot Nothing Then


                stream = response.GetResponseStream

                If charEncoding IsNot Nothing Then
                    navigator = CreateSafeNavigator(stream, charEncoding)
                Else
                    navigator = CreateSafeNavigator(stream)
                End If

            End If
        End Using

        Return navigator
    End Function


    ''' <summary>
    ''' Creates a XPathNavigator against the supplied Stream
    ''' </summary>
    ''' <remarks>Translated code extract to VB from Argotic SyndicationEncodingUtility Class
    ''' http://www.codeplex.com/Argotic
    ''' </remarks>
    Public Overloads Shared Function CreateSafeNavigator(ByVal stream As Stream) As XPathNavigator

        Dim charEncoding As Encoding = Encoding.UTF8
        Dim buffer As Byte() = Nothing

        Dim navigator As XPathNavigator = Nothing
        If stream IsNot Nothing Then

            buffer = GetStreamBytes(stream)

            charEncoding = GetXmlEncoding(buffer)

            Using memStream As MemoryStream = New MemoryStream(buffer)
                navigator = CreateSafeNavigator(memStream, charEncoding)
            End Using

        End If

        Return navigator
    End Function


    ''' <summary>
    ''' Creates a XPathNavigator against the supplied Stream
    ''' </summary>
    ''' <remarks>Translated code extract to VB from Argotic SyndicationEncodingUtility Class
    ''' http://www.codeplex.com/Argotic
    ''' </remarks>
    Public Overloads Shared Function CreateSafeNavigator(ByVal memStream As Stream, ByVal charEncoding As Encoding) As XPathNavigator

        Dim navigator As XPathNavigator = Nothing
        If memStream IsNot Nothing And charEncoding IsNot Nothing Then

            Using reader As StreamReader = New StreamReader(memStream, charEncoding)
                navigator = CreateSafeNavigator(reader.ReadToEnd)
            End Using

        End If

        Return navigator
    End Function


    ''' <summary>
    ''' Creates a XPathNavigator against the supplied Stream
    ''' </summary>
    ''' <remarks>Translated code extract to VB from Argotic SyndicationEncodingUtility Class
    ''' http://www.codeplex.com/Argotic
    ''' </remarks>
    Public Overloads Shared Function CreateSafeNavigator(ByVal xml As String) As XPathNavigator

        Dim navigator As XPathNavigator = Nothing

        If Not String.IsNullOrEmpty(xml) Then

            Dim safeXml As String = RemoveInvalidXmlHexadecimalCharacters(xml)

            Using reader As StringReader = New StringReader(safeXml)
                Try
                    Dim document As XPathDocument = New XPathDocument(reader)
                    navigator = document.CreateNavigator()
                Catch ex As Exception

                End Try                
            End Using

        End If

        Return navigator

    End Function

    Public Shared Function CreateSafeDocument(ByVal xml As String) As XmlDocument

        Dim document As XmlDocument = Nothing

        If Not String.IsNullOrEmpty(xml) Then

            Dim safeXml As String = RemoveInvalidXmlHexadecimalCharacters(xml)

            document = New XmlDocument()

            Try
                document.LoadXml(safeXml)
            Catch ex As Exception

            End Try

            
        End If

        Return document
    End Function

#End Region


#Region "Encoding"



    ''' <summary>
    ''' Creates a XPathNavigator against the supplied Stream
    ''' </summary>
    ''' <remarks>Translated code extract to VB from Argotic SyndicationEncodingUtility Class
    ''' http://www.codeplex.com/Argotic
    ''' </remarks>
    Private Shared Function GetStreamBytes(ByVal stream As Stream) As Byte()

        Dim intialLength As Integer = 32768
        Dim read As Integer = 0
        Dim chunk As Integer = Nothing

        Dim buffer As Byte()
        ReDim buffer(intialLength)

        If stream IsNot Nothing Then


            While (chunk = stream.Read(buffer, read, buffer.Length - read)) > 0

                read += chunk

                ' Determine if stream larger than default buffer size
                If read = buffer.Length Then

                    Dim nextByte = stream.ReadByte

                    ' if at end of stream, return result
                    If nextByte = -1 Then
                        Return buffer
                    End If

                    Dim newBuffer As Byte()
                    ReDim newBuffer(buffer.Length * 2)
                    Array.Copy(buffer, newBuffer, buffer.Length)
                    newBuffer(read) = CType(nextByte, Byte)

                    buffer = newBuffer
                    read += 1
                End If

            End While

        End If

        ' Shrink buffer prior to returning result 
        Dim result As Byte()
        ReDim result(read)
        Array.Copy(buffer, result, read)

        Return result

    End Function


    ''' <summary>
    ''' Sanitizes the supplied string so that it can be safely represented in XML.
    ''' </summary>
    ''' <remarks>Translated code extract to VB from Argotic SyndicationEncodingUtility Class
    ''' http://www.codeplex.com/Argotic
    ''' </remarks>
    Public Shared Function RemoveInvalidXmlHexadecimalCharacters(ByVal content As String) As String

        Dim invalidXmlHexadecimalCharacters As Regex = New Regex("#x((10?|[2-F])FFF[EF]|FDD[0-9A-F]|7F|8[0-46-9A-F]9[0-9A-F])")

        If Not String.IsNullOrEmpty(content) Then

            Return invalidXmlHexadecimalCharacters.Replace(content, String.Empty)
        Else
            Return content

        End If
    End Function

    ''' <summary>
    ''' Returns an Encoding that represents the XML character encoding for the supplied Stream
    ''' </summary>
    ''' <remarks>Translated code extract to VB from Argotic SyndicationEncodingUtility Class
    ''' http://www.codeplex.com/Argotic
    ''' </remarks>
    Private Overloads Shared Function GetXmlEncoding(ByVal data As Byte()) As Encoding
        Dim charEncoding As Encoding = Nothing
        If data IsNot Nothing Then

            Using memStream As MemoryStream = New MemoryStream(data)
                charEncoding = GetXmlEncoding(memStream)
            End Using

        End If
        Return charEncoding
    End Function

    ''' <summary>
    ''' Returns an Encoding that represents the XML character encoding for the supplied Stream
    ''' </summary>
    ''' <remarks>Translated code extract to VB from Argotic SyndicationEncodingUtility Class
    ''' http://www.codeplex.com/Argotic
    ''' </remarks>
    Private Overloads Shared Function GetXmlEncoding(ByVal memStream As Stream) As Encoding
        Dim charEncoding As Encoding = Nothing

        If memStream IsNot Nothing Then
            Using reader As StreamReader = New StreamReader(memStream)
                charEncoding = GetXmlEncoding(reader.ReadToEnd)
            End Using
        End If

        Return charEncoding
    End Function

    ''' <summary>
    ''' Returns an Encoding that represents the XML character encoding for the supplied Stream
    ''' </summary>
    ''' <remarks>Translated code extract to VB from Argotic SyndicationEncodingUtility Class
    ''' http://www.codeplex.com/Argotic
    ''' </remarks>
    Private Overloads Shared Function GetXmlEncoding(ByVal content As String) As Encoding

        Dim charEncoding As Encoding = Encoding.UTF8
        Dim encodingPattern As String = "^<\?xml.+?encoding\s*=\s*(?:""(?<webName>[^""]*)""|(?<webName>\S+)).*?\?>"

        If content IsNot Nothing Then

            Dim encodingMatch As Match = Regex.Match(content, encodingPattern, RegexOptions.IgnoreCase Or RegexOptions.Singleline)

            If encodingMatch IsNot Nothing And encodingMatch.Groups.Count > 0 Then

                Dim group As Group = encodingMatch.Groups("webName")
                If group IsNot Nothing Then
                    Try
                        charEncoding = Encoding.GetEncoding(group.Value)
                    Catch ex As Exception
                        charEncoding = Encoding.UTF8
                    End Try
                End If

            End If

        End If

        Return charEncoding
    End Function


#End Region


End Class
